/**
 * 处理聊天容器的自动滚动行为
 *
 * 当用户接近底部时（距离底部小于容器高度的15%），在有新消息添加后自动滚动到底部
 * 当用户正在查看历史消息时，保持当前滚动位置
 *
 * @param {HTMLElement} container - 消息容器元素
 *
 * @param {Object} options - 配置选项
 *
 * @param {number} [options.threshold=0.1] - 触发滚动的阈值比例（相对于容器高度）
 *
 * @param {boolean} [options.smooth=true] - 是否使用平滑滚动效果
 *
 * @returns {boolean} - 是否执行了滚动操作
 */
function autoScrollToBottom(container, options = {}) {
	// 参数验证
	if (!(container instanceof HTMLElement)) {
		console.error('容器必须是有效的HTML元素');
		return false;
	}
	// 设置默认选项
	const { threshold = 0.15, smooth = true } = options;
	/**
	 * 获取消息容器的滚动高度减去滚动位置和容器高度的差值
	 */
	const distanceToBottom = container.scrollHeight - container.scrollTop - container.clientHeight;
	/**
	 * 滚动阈值，用于判断用户是否接近底部
	 */
	const scrollThreshold = container.clientHeight * threshold;
	// 只有当用户接近底部时, 执行滚动
	if (distanceToBottom <= scrollThreshold) {
		container.scrollTo({ top: container.scrollHeight, behavior: smooth ? 'smooth' : 'auto' });
		return true;
	}
	return false;
};

/**
 * 提取文章的结论
 *
 * 该函数会尝试从给定的文本内容中提取文章的结论部分。
 *
 * 首先会尝试匹配包含 <think> 标签的推理过程部分，提取标签后的内容作为结论。
 *
 * 若未找到 <think> 标签，会尝试匹配包含特定 HTML 类名 "conclusion" 的结论部分，提取其中的文本内容。
 *
 * 若仍然没有找到符合格式的结论部分，将返回原始文本内容。
 *
 * @param {string} content - 包含推理过程和结论的文本内容
 *
 * @returns {string} 提取到的结论文本内容
 */
function extractConclusion(content) {
	/**
	 * 尝试匹配 <think> 标签及其内容，以及标签后的结论部分
	 * 正则表达式解释：
	 * - <think> 匹配 <think> 标签开头
	 * - ([\s\S]*?) 匹配 <think> 和 </think> 之间的任意内容，非贪婪模式
	 * - <\/think> 匹配 </think> 标签结尾
	 * - ([\s\S]*) 匹配 </think> 标签后的所有内容
	 */
	const thinkMatch = content.match(/<think>([\s\S]*?)<\/think>([\s\S]*)/);
	// 如果匹配成功，获取 </think> 标签后的内容并去除首尾空白后返回
	if (thinkMatch) return thinkMatch[2].trim();
	/**
	 * 尝试匹配类名为 "conclusion" 的 div 元素
	 * 正则表达式解释：
	 * - <div class="conclusion"> 匹配类名为 "conclusion" 的 div 标签开头
	 * - ([\s\S]*?) 匹配 div 标签内的任意内容，非贪婪模式
	 * - <\/div> 匹配 div 标签结尾
	 * - i 标志表示不区分大小写
	 */
	const conclusionMatch = content.match(/<div class="conclusion">([\s\S]*?)<\/div>/i);
	// 如果匹配成功，移除匹配到的内容中的 HTML 标签，去除首尾空白后返回
	if (conclusionMatch) return conclusionMatch[1].replace(/<[^>]*>/g, "").trim();

	// 如果以上两种匹配都未成功，说明没有找到特定格式的结论，返回原始文本内容
	return content;
};

/**
 * 处理文本中的思考标签，将其转换为特定格式的 HTML 结构
 *
 * 该函数会尝试匹配两种思考标签格式：
 * 1. <think>...</think> 格式
 * 2. <|thought_start|>...<|thought_end|> 格式
 *
 * 如果匹配成功，会将思考内容渲染并包装成特定的 HTML 结构，剩余内容作为结论处理。
 * 如果未匹配到思考标签，会直接解析内容并为表格元素添加 "markdown-table" 类名。
 *
 * @param {string} content - 包含思考标签的文本内容
 * @returns {string} 处理后的 HTML 内容
 */
function processThinkTags(content) {
	/**
	 * 定义匹配思考标签的正则表达式模式数组
	 * 每个模式包含正则表达式和对应的格式名称
	 */
	const patterns = [
		{ regex: /<think>([\s\S]*?)<\/think>([\s\S]*)/, format: 'think' },
		{ regex: /<\|thought_start\|>([\s\S]*?)<\|thought_end\|>([\s\S]*)/, format: 'thought' }
	];
	// 遍历所有模式，尝试匹配思考标签
	for (const pattern of patterns) {
		/**
		 * 尝试匹配当前模式的思考标签
		 */
		const match = content.match(pattern.regex);
		// 如果匹配成功，执行以下操作
		if (match) {
			/**
			 * 提取思考内容
			 */
			const thinkContent = match[1];
			/**
			 * 提取思考标签后的剩余内容，若不存在则为空字符串
			 */
			const remainingContent = match[2] || '';
			/**
			 * 对思考内容进行 Markdown 解析，去除首尾空白
			 */
			const renderedThink = marked.parse(thinkContent.trim());
			/**
			 * 对剩余内容进行 Markdown 解析，若剩余内容为空则不解析，去除首尾空白
			 */
			const renderedRemaining = remainingContent.trim() ? marked.parse(remainingContent.trim()) : '';
			// 返回包装好的 HTML 结构
			return `
                <div class="think-block">
                    <div class="think-header">
                        <span><i class="fas fa-lightbulb think-icon"></i> 推理过程</span>
                        <button class="chat-action-button toggle_think_button" style="font-size: 1.35rem;">
                            <i class="fas fa-angle-down"></i>
                        </button>
                    </div>
                    <div class="think-content">
                        ${renderedThink}
                    </div>
                </div>
                <div class="conclusion">${renderedRemaining}</div>
            `;
		}
	};
	/**
	 * 没有匹配到思考标签时，直接对原始内容进行 Markdown 解析
	 */
	let processedContent = marked.parse(content);
	// 为所有表格元素添加 "markdown-table" 类名，方便样式控制
	processedContent = processedContent.replace(/<table(\s[^>]*)?>/gi, (_, attrs) => `<table class="markdown-table"${attrs ? ' ' + attrs.trim() : ''}>`);
	// 返回处理后的 HTML 内容
	return processedContent;
};

/**
 * 清理文本，用于语音合成
 *
 * @param {string} text - 输入的文本
 *
 * @returns {string} - 清理后的文本
 */
function cleanTextForTTS(text) {
	// 如果输入文本为空，直接返回空字符串
	if (!text) return "";
	// 移除英文括号及其内的内容
	let cleanedText = text.replace(/\([^)]*\)/g, "");
	// 移除中文括号及其内的内容
	cleanedText = cleanedText.replace(/\（[^）]*\）/g, "");
	// 移除 HTML 标签
	cleanedText = cleanedText.replace(/<[^>]+>/g, "");
	// 移除代码块（由 ``` 包裹的内容）
	cleanedText = cleanedText.replace(/```[\s\S]*?```/g, "");
	// 移除 Markdown 图片语法
	cleanedText = cleanedText.replace(/!\[.*?\]\(.*?\)/g, "");
	// 移除 Emoji 表情符号
	cleanedText = cleanedText.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]|[\u2600-\u27FF]|[\uD83C][\uDF00-\uDFFF]|[\uD83D][\uDC00-\uDDFF]/g, "");
	// 移除单星号包裹的内容（非加粗或斜体语法）
	cleanedText = cleanedText.replace(/(?<!\*)\*[^*]*\*(?!\*)/g, "");
	// 移除 * 与 # 字符
	cleanedText = cleanedText.replace(/[*#]/g, "");
	// 将多个连续空格替换为单个空格，并去除首尾空格
	return cleanedText.replace(/\s{2,}/g, " ").trim();
};

/**
 * 创建消息对象，包含时间戳、是否为提示、是否不渲染、角色和内容
 *
 * @param {string} role - 消息的角色，例如 'user', 'assistant' 等
 *
 * @param {string} content - 消息的内容
 *
 * @param {boolean} [recorded=true] - 是否将消息记录到对话历史中，默认为 true
 *
 * @param {boolean} [isPrompt=false] - 消息是否为提示，默认为 false
 *
 * @param {boolean} [noRender=false] - 消息是否不进行渲染，默认为 false
 *
 * @param {string} [imageUrl=null] - 图片消息的 URL，默认为 null (用于在消息中渲染图片)
 *
 * @param {string} [fileUrl=null] - 文件绑定的 URL，默认为 null (用于在删除消息时删除文件)
 *
 * @returns {Object} 包含消息信息的对象
 */
function createMessageObject(role, content, recorded = true, isPrompt = false, noRender = false, imageUrl = null, fileUrl = null) {
	/**
	 * 创建消息对象，包含时间戳、是否为提示、是否不渲染、角色和内容
	 */
	const message = {
		role,
		content,
		timestamp: getSystemTime(),
		isPrompt,
		noRender,
		imageUrl,
		fileUrl,
	};
	// 如果需要记录，则将消息添加到对话历史记录中
	if (recorded) conversationHistory.push(message);
	// 返回创建的消息对象
	return message;
};